package com.qrlib.decode.impl;

import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import com.qrlib.utils.LogUtils;
import com.tencent.qbar.QbarNative;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * 严格控制只有一个实例 Created by zhengxiao on 01/09/2017.
 */

public class QbarDaemon implements Callback {

  static {
    System.loadLibrary("QrMod");
  }


  private static class Holder {

    private static WeakReference<QbarDaemon> INSTANCE = new WeakReference<>(new QbarDaemon());
  }

  public static synchronized QbarDaemon getInstance() {
    return Holder.INSTANCE.get();
  }

  private static final int WHAT_DECODE_SUCCESS = 0;
  private static final int WHAT_DECODE_FAILED = 1;
  private static final int WHAT_DECODE_REQUEST = 2;


  /**
   * can only exist one instance
   */
  private QbarNative qbarNative;
  private final Handler handler;
  private final Handler mainHandler = new Handler(Looper.getMainLooper(), this); // handleMessage()
  private long runningDecodeTime = 0;
  private long decodeCount = 0;
  private ByteBuffer outImgBuffer;

  private QbarDaemon() {
    // qr code initialize
    qbarNative = new QbarNative();
    qbarNative
        .init(QbarNative.TRYHARDER, QbarNative.SEARCH_ONE, QbarNative.SCAN_VIDEO, "ANY", "UTF-8");

    int[] readers = new int[1];
    readers[WHAT_DECODE_SUCCESS] = QbarNative.QRCODE;
    qbarNative.setReaders(readers, readers.length);

    HandlerThread thread = new HandlerThread("qbar", Thread.MIN_PRIORITY + 1);
    thread.start();
    handler = new Handler(thread.getLooper(), this);
    LogUtils.i("QbarDaemon 已启动");
  }

  @Override
  protected void finalize() throws Throwable {
    super.finalize();
    handler.getLooper().quit();
    try {
      qbarNative.release();
    } catch (Exception e) {
    }
    LogUtils.i("QbarDaemon 已销毁, statistics: decode avg time per sample = " + runningDecodeTime
        + "ns, total " + decodeCount + " times");
  }

  /**
   * note: callback 一定在主线程回调
   */
  public void postRequest(ScanFrameRequest request) {
    Message.obtain(handler, WHAT_DECODE_REQUEST, request).sendToTarget();
  }

  @Override
  public boolean handleMessage(Message msg) {
    if (msg == null) {
      return false;
    }
    switch (msg.what) {
      // worker thread
      case WHAT_DECODE_REQUEST: {
        if (qbarNative == null) {
          return false;
        }

        long startNanoTime = System.nanoTime();
        if (!(msg.obj instanceof ScanFrameRequest)) {
          return false;
        }
        //测量解码时间
        long decodeStarMillisecond = System.currentTimeMillis();
        ScanFrameRequest sharedFrame = (ScanFrameRequest) msg.obj;

        // do scan

        // judge if we should subsample
        boolean needSubSample =
            Math.min(sharedFrame.crop.width(), sharedFrame.crop.height()) >= 1080;
//        Log.i("QbarDaemon", "needSubSample: " + needSubSample + ", crop = " + sharedFrame.crop);

        // qr image pre-process
        if (outImgBuffer == null) {//使用nio减少heap占用
          outImgBuffer = ByteBuffer.allocateDirect(sharedFrame.width * sharedFrame.height * 3 / 2)
              .order(ByteOrder.nativeOrder());
        } else {
          outImgBuffer.clear();
        }
        int[] outImgSize = new int[2];
        int cropResult = QbarNative.gray_rotate_crop_sub(
            outImgBuffer.array(),
            outImgSize,
            sharedFrame.data,
            sharedFrame.width,
            sharedFrame.height,
            sharedFrame.crop.left, sharedFrame.crop.top,
            sharedFrame.crop.width(), sharedFrame.crop.height(),
            sharedFrame.rotate ? QbarNative.ROTATE_0 : QbarNative.ROTATE_90,
            needSubSample ? QbarNative.SUBSAMPLE : QbarNative.NOSUBSAMPLE);

        if (cropResult == 1) {
          // qr scan image, the engine prefer 400/600 sized image

          int result = qbarNative
              .scanImage(outImgBuffer.array(), outImgSize[0], outImgSize[1], QbarNative.GRAY);

          if (result == 1) {
            StringBuilder stype = new StringBuilder(); // maybe QR_CODE
            StringBuilder sdata = new StringBuilder(); // the content, maybe an url
            int gResult = qbarNative.GetOneResult(stype, sdata);
            if (gResult > 0) {
              final String qrCodeData = sdata.toString();
              calculateRunningStats(System.nanoTime() - startNanoTime);
              sharedFrame.decodeResult = qrCodeData;
              long decodeTime = System.currentTimeMillis() - decodeStarMillisecond;
              LogUtils.i("onDecodeSuccess: 扫码用时 " + decodeTime + " ms");
              //todo add crop value
//              if (sharedFrame.callback != null) {
//                sharedFrame.callback
//                    .onDecodeSuccessUpLoadTime();
//              }
              Message.obtain(mainHandler, WHAT_DECODE_SUCCESS, sharedFrame).sendToTarget();
              outImgBuffer.clear();
              return true;
            }
          }
        }
        calculateRunningStats(System.nanoTime() - startNanoTime);
        Message.obtain(mainHandler, WHAT_DECODE_FAILED, sharedFrame).sendToTarget();
        outImgBuffer.clear();
        System.gc();
        return true;
      }
      //main thread
      case WHAT_DECODE_SUCCESS: {
        if (msg.obj instanceof ScanFrameRequest) {
          ScanFrameRequest data = (ScanFrameRequest) msg.obj;
          if (data.callback != null) {
            data.callback.onDecodeSuccess(data.decodeResult);
          }
          return true;
        }
      }
      break;
      //main thread
      case WHAT_DECODE_FAILED: {
        LogUtils.d(String
            .format("decode------->fail hasQr:%s = qrArea:%s", qbarNative.CheckHasQrcode(),
                qbarNative
                    .GetQrcodeAreaRatio()));
        if (qbarNative.CheckHasQrcode() == 1 && qbarNative.GetQrcodeAreaRatio() > 0) {
          ScanFrameRequest data = (ScanFrameRequest) msg.obj;
          data.callback.onNeedAdjustCamera();
          return false;
        }
        if (msg.obj instanceof ScanFrameRequest) {
          ScanFrameRequest data = (ScanFrameRequest) msg.obj;

          if (data.callback != null) {
            data.callback.onDecodeFail();
          }
          return true;
        }
      }
      break;
      default:
    }
    return false;
  }

  private void calculateRunningStats(long timeCost) {
    if (decodeCount == Long.MAX_VALUE) {
      decodeCount = 0L;
      runningDecodeTime = 0L;
    }
    runningDecodeTime = runningDecodeTime * decodeCount / (decodeCount + 1) +
        timeCost / (decodeCount + 1);
    decodeCount++;
  }

  public Bitmap encodeQR(String content, int size) {
    try {
      return QbarNative.encode(content, size, size, QbarNative.QR_CODE, QbarNative.M, "UTF-8", 1);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }
}